import * as THREE from 'three';
import { World } from './World';
import { IUpdatable } from '../interface/IUpdatable';
import { SkyShader, CSM } from './../../lib';

export class Sky extends THREE.Object3D implements IUpdatable {

    public updateOrder: number = 1;
    public sunPosition = new THREE.Vector3();
    public csm: CSM;

    private _phi: number = 50;
    private _theta: number = 145;

    private hemiLight: THREE.HemisphereLight;
    private maxHemiIntensity: number = 0.9;
    private minHemiIntensity: number = 0.3;

    private skyMesh: THREE.Mesh;
    private skyMaterial: THREE.ShaderMaterial;

    private world: World;

    set theta ( value: number ) {
        this._theta = value;
        this.refreshSunPosition();
    }

    set phi ( value: number ) {
        this._phi = value;
        this.refreshSunPosition();
        this.refreshHemiIntensity();
    }

    constructor ( world: World ) {
        super();

        this.world = world;

        this.skyMaterial = new THREE.ShaderMaterial( {
            uniforms: THREE.UniformsUtils.clone( SkyShader.uniforms ),
            fragmentShader: SkyShader.fragmentShader,
            vertexShader: SkyShader.vertexShader,
            side: THREE.BackSide
        } );

        this.skyMesh = new THREE.Mesh( new THREE.SphereGeometry( 1000, 24, 12 ), this.skyMaterial );
        this.attach( this.skyMesh );

        this.hemiLight = new THREE.HemisphereLight( 0XFFFFFF, 0XFFFFFF, 1.0 );

        this.hemiLight.color.setHSL( 0.59, 0.4, 0.6 );
        this.hemiLight.groundColor.setHSL( 0.095, 0.2, 0.75 );
        this.hemiLight.position.set( 0, 50, 0 );
        this.world.graphicsWorld.add( this.hemiLight );

        // CSM
        // New version
        let splitsCallback = ( cascades: number, near: number, far: number, breaks: number[] ) => {
            for ( let i = cascades - 1; i >= 0; i-- ) {
                breaks.push( Math.pow( 1 / 3, i ) );
            }
        };

        this.csm = new CSM( {
            lightFar: 250,
            lightIntensity: 2.5,
            cascades: 3,
            shadowMapSize: 2048,
            camera: world.camera,
            parent: world.graphicsWorld,
            mode: "custom",
            customSplitsCallback: splitsCallback,

        } );
        this.csm.fade = true;

        this.refreshSunPosition();

        world.graphicsWorld.add( this );
        world.registerUpdatable( this );
    }

    public update ( timeStep: number, unscaledTimeStep: number ): void {
        this.position.copy( this.world.camera.position );
        this.refreshSunPosition();

        this.csm.update();
        this.csm.lightDirection = this.sunPosition.clone().negate().normalize();
    }

    public refreshSunPosition () {

        const sin = Math.sin, cos = Math.cos, d = Math.PI / 180, sunDistance = 10;

        this.sunPosition.x = sunDistance * sin( this._theta * d ) * cos( this._phi * d );
        this.sunPosition.y = sunDistance * sin( this._phi * d );
        this.sunPosition.z = sunDistance * cos( this._theta * d ) * cos( this._phi * d );

        this.skyMaterial.uniforms.sunPosition.value.copy( this.sunPosition );
        this.skyMaterial.uniforms.cameraPos.value.copy( this.world.camera.position );

    }

    public refreshHemiIntensity () {
        this.hemiLight.intensity = this.minHemiIntensity + Math.pow( 1 - ( Math.abs( this._phi - 90 ) / 90 ), 0.25 ) * ( this.maxHemiIntensity - this.minHemiIntensity );
    }
}